import { OhosModuleContext } from "@ohos/hvigor-ohos-plugin/src/plugin/context/plugin-context";
import RouterServiceConfig from "./RouterServiceMapConfig";
import FileUtils from "../utils/FileUtils";
import {
  Decorator,
  Expression,
  Project,
  PropertyAssignment,
  SourceFile,
  SyntaxKind,
} from "ts-morph";
import { HvigorNode } from "@ohos/hvigor";
import path from "path";

export let tsProject: Project = new Project();

interface ServiceClassMetadata {
  name: string;
  singleton?: boolean;
}

interface RouterUriMetadata {
  name?: string;
  host?: string;
  path?: string;
  scheme?: string;
  regex?: RegExp;
  interceptors?: string[];
}

export default class RouterClassParser {
  constructor(private context: OhosModuleContext, private node: HvigorNode) {}
  parse(): RouterServiceConfig | undefined {
    const projectRootPath = this.context.getModulePath();
    const scanPath = `${projectRootPath}/src/main/ets`;

    const routerServiceConfig: RouterServiceConfig = new RouterServiceConfig(
      this.context,
      this.node
    );

    const scanFiles = FileUtils.walkSync(scanPath, /\.(ts|ets)$/);

    if (!scanFiles.length) {
      return undefined;
    }

    scanFiles
      .map((file) => {
        return tsProject.addSourceFilesAtPaths(file);
      })
      .reduce((prev, curr) => {
        return prev.concat(curr);
      })
      .map((sourceFile) => {
        return new RouterClassSourceFileParse(
          tsProject,
          sourceFile,
          this.node,
          routerServiceConfig
        );
      })

      .forEach((item) => {
        item.parse();
      });

    return routerServiceConfig;
  }
}

class RouterClassSourceFileParse {
  private importMap: Map<string, string[]> = new Map();
  private sourceFilePath: string;

  constructor(
    private tsProject: Project,
    private sourceFile: SourceFile,
    private node: HvigorNode,
    private routerServiceConfig: RouterServiceConfig
  ) {
    this.sourceFilePath = this.sourceFile.getFilePath();
  }

  parse() {
    this.parseImport();
    this.parseServiceClass();
    this.parseInterceptor();
    this.parseRouterUri();
  }

  private parseImport() {
    this.sourceFile.getImportDeclarations().forEach((importDeclaration) => {
      const moduleSpecifier = importDeclaration.getModuleSpecifierValue();
      const namedImports = importDeclaration
        .getNamedImports()
        .map((namedImport) => namedImport.getName());
      const defaultImport = importDeclaration.getDefaultImport()?.getText();
      const namespaceImport = importDeclaration.getNamespaceImport()?.getText();
      const importNames: string[] = [];

      if (namedImports.length > 0) {
        importNames.push(...namedImports);
      }
      if (defaultImport) {
        importNames.push(defaultImport);
      }
      if (namespaceImport) {
        importNames.push(namespaceImport);
      }

      if (importNames.length > 0) {
        if (this.importMap.has(moduleSpecifier)) {
          const existingImports = this.importMap.get(moduleSpecifier)!;
          this.importMap.set(moduleSpecifier, [
            ...new Set([...existingImports, ...importNames]),
          ]);
        } else {
          this.importMap.set(moduleSpecifier, importNames);
        }
      }
    });
  }

  private parseServiceClass() {
    this.sourceFile
      .getClasses()
      .filter(
        (cls) =>
          !!cls.getDecorator("ServiceClass") &&
          this.isImportFrom("ServiceClass", "@newlink/navigation")
      )
      .forEach((cls) => {
        const decorator = cls.getDecorator("ServiceClass");
        const serviceClassMetadata: ServiceClassMetadata =
          this.parseDecoratorArguments(decorator);

        this.routerServiceConfig.pushServiceInfo({
          serviceName: serviceClassMetadata.name,
          className: cls.isDefaultExport() ? "default" : cls.getName(),
          classFilePath: cls
            .getSourceFile()
            .getFilePath()
            .replace(this.node.getNodePath(), ""),
          singleton: serviceClassMetadata.singleton,
          metadata: JSON.stringify(serviceClassMetadata),
        });
      });
  }

  private parseInterceptor() {
    this.sourceFile
      .getClasses()
      .filter(
        (cls) =>
          !!cls.getDecorator("RouterInterceptor") &&
          this.isImportFrom("RouterInterceptor", "@newlink/navigation")
      )
      .forEach((cls) => {
        const decorator = cls.getDecorator("RouterInterceptor");
        const serviceClassMetadata: ServiceClassMetadata =
          this.parseDecoratorArguments(decorator);

        this.routerServiceConfig.pushInterceptor({
          name: serviceClassMetadata.name,
          className: cls.isDefaultExport() ? "default" : cls.getName(),
          classFilePath: cls
            .getSourceFile()
            .getFilePath()
            .replace(this.node.getNodePath(), ""),
          metadata: JSON.stringify(serviceClassMetadata),
        });
      });
  }

  private parseRouterUri() {
    this.parseClassRouterUri();
    this.parserPageRouterUri();
  }

  private parseClassRouterUri() {
    this.sourceFile
      .getClasses()
      .filter(
        (cls) =>
          !!cls.getDecorator("RouterUri") &&
          this.isImportFrom("RouterUri", "@newlink/navigation")
      )
      .forEach((cls) => {
        const decorator = cls.getDecorator("RouterUri");
        const routerUriMetadata: RouterUriMetadata =
          this.parseDecoratorArguments(decorator);

        this.routerServiceConfig.pushRouterUri({
          type: "uriHandler",
          imports: {
            name: cls.getName(),
            isDefault: cls.isDefaultExport(),
            path: path
              .relative(
                `${this.node.getNodeDir().getPath()}/src/main/ets/generated`,
                cls.getSourceFile().getFilePath()
              )
              .replace(/\.(ts|ets)$/, ""),
          },
          metadata: routerUriMetadata,
        });
      });
  }

  private parserPageRouterUri() {
    let componentNodes = this.sourceFile
      .getChildrenOfKind(SyntaxKind.ExpressionStatement)
      .filter((node) => {
        return node.getText() == "struct";
      })
      .map((node) => {
        return node.getNextSibling().getText();
      });

    this.sourceFile
      .getChildrenOfKind(SyntaxKind.MissingDeclaration)
      .forEach((node, index) => {
        const decorator = node
          .getChildrenOfKind(SyntaxKind.Decorator)
          .find((node) => node.getName() == "RouterUri");
        if (decorator) {
          const routerUriMetadata: RouterUriMetadata =
            this.parseDecoratorArguments(decorator);

          this.routerServiceConfig.pushRouterUri({
            type: "component",
            imports: {
              name: componentNodes[index],
              isDefault: false,
              path: path
                .relative(
                  `${this.node.getNodeDir().getPath()}/src/main/ets/generated`,
                  this.sourceFile.getFilePath()
                )
                .replace(/\.(ts|ets)$/, ""),
            },
            metadata: routerUriMetadata,
          });
        }
      });
  }

  private parseDecoratorArguments(decorator: Decorator): any {
    let argResult = {};
    decorator.getArguments().map((arg) => {
      const objLiteral = arg.asKind(SyntaxKind.ObjectLiteralExpression);
      if (objLiteral) {
        objLiteral.getProperties().forEach((prop) => {
          let propertyName = (prop as PropertyAssignment).getName();
          let propertyValue = this.parseIdentifierPropertyValue(
            (prop as PropertyAssignment).getInitializer()!
          );
          Reflect.set(argResult, propertyName, propertyValue);
        });
      }
    });
    return argResult;
  }

  private parseIdentifierPropertyValue(value: Expression): any {
    switch (value.getKind()) {
      case SyntaxKind.Identifier:
        return this.parseConstantValue(
          value.getText(),
          this.getVariableFilePath(value.getText())
        );
      case SyntaxKind.PropertyAccessExpression:
        return this.parseConstantValue(
          value.getText(),
          this.getVariableFilePath(value.getText())
        );
      case SyntaxKind.ArrayLiteralExpression:
        return value
          .asKind(SyntaxKind.ArrayLiteralExpression)
          ?.getElements()
          .map((item) => this.parseIdentifierPropertyValue(item));
      default:
        return this.parsePrimitiveValue(value);
    }
  }

  private parseConstantValue(variableName: string, filePath: string): any {
    const sourceFile = this.tsProject.addSourceFileAtPath(filePath);
    const variableDeclarations = sourceFile
      .getVariableDeclarations()
      .filter((item) => item.getName() == variableName);

    if (variableDeclarations.length > 0) {
      const initializer = variableDeclarations[0].getInitializer();

      if (initializer) {
        return this.parsePrimitiveValue(initializer);
      }
    }

    return null;
  }

  private getVariableFilePath(variableName: string): string {
    let filePath: string = "";
    // 检查本地变量和类
    let classesNames = this.sourceFile.getClasses().map((classes) => {
      return classes.getName()!;
    });
    let variableNames = this.sourceFile
      .getVariableDeclarations()
      .map((variableDeclaration) => {
        return variableDeclaration.getName();
      });
    if (
      classesNames.includes(variableName) ||
      variableNames.includes(variableName)
    ) {
      return this.sourceFile.getFilePath();
    }

    // Search in import map
    for (const [importPath, importNames] of this.importMap.entries()) {
      if (importNames.includes(variableName)) {
        try {
          let currentDir = FileUtils.pathResolve(this.sourceFilePath, "../");
          // Handle potential path format issues
          let normalizedImportPath = importPath.replace(/\.+\/+/g, "./");
          let tempFilePath = FileUtils.pathResolve(
            currentDir,
            normalizedImportPath + ".ets"
          );

          if (FileUtils.exist(tempFilePath)) {
            filePath = tempFilePath;
            break; // Exit loop after finding
          } else {
            // Try other modules
            /*  try {
              filePath = this.getOtherModuleVariableFilePath(
                importPath,
                variableName
              );
              if (filePath) break; // Exit loop after finding
            } catch (error: any) {
             
            } */
          }
        } catch (error: any) {}
      }
    }

    return filePath;
  }

  private parsePrimitiveValue(value: Expression): any {
    let propertyValue;
    switch (value.getKind()) {
      case SyntaxKind.RegularExpressionLiteral:
        propertyValue = value
          .asKind(SyntaxKind.RegularExpressionLiteral)
          .getLiteralValue();
        break;
      case SyntaxKind.StringLiteral:
        propertyValue = value
          .asKind(SyntaxKind.StringLiteral)
          ?.getLiteralValue();
        break;
      case SyntaxKind.NumericLiteral:
        propertyValue = value
          .asKind(SyntaxKind.NumericLiteral)
          ?.getLiteralValue();
        break;
      case SyntaxKind.TrueKeyword:
        propertyValue = true;
        break;
      case SyntaxKind.FalseKeyword:
        propertyValue = false;
        break;
      case SyntaxKind.ArrayLiteralExpression:
        propertyValue = value
          .asKind(SyntaxKind.ArrayLiteralExpression)
          ?.getElements()
          .map((item) =>
            item.asKind(SyntaxKind.StringLiteral)?.getLiteralValue()
          );
        break;
    }
    return propertyValue;
  }

  private isImportFrom(variableName: string, moduleName: string): boolean {
    for (const [importPath, importNames] of this.importMap.entries()) {
      if (importNames.includes(variableName) && importPath == moduleName) {
        return true;
      }
    }

    return false;
  }

  /*  private getOtherModuleVariableFilePath(moduleName: string, variableName: string): string {
    let moduleFilePath = FileUtils.pathResolve(this.config.modulePath,
      'oh_modules', moduleName, 'src/main/ets');
    if (!FileUtils.exist(moduleFilePath)) {
      // Indicates that the module does not exist and needs to search the project directory
      moduleFilePath = FileUtils.pathResolve(PluginStore.getInstance().projectFilePath,
        'oh_modules', moduleName, 'src/main/ets');
    }
    let variableMap;
    if (PluginStore.getInstance().variableCache.has(moduleName)) {
      variableMap = PluginStore.getInstance().variableCache.get(moduleName)!;
    } else {
      variableMap =
        TsAstUtil.parseCrossModuleVariable(moduleFilePath);
      PluginStore.getInstance().variableCache.set(moduleName, variableMap);
    }
    for (let [key, value] of variableMap) {
      if (value.includes(variableName)) {
        return key;
      }
    }
    throw new Error(`Unknown variable ${variableName} in ${moduleName}`);
  } */
}
